﻿Imports System.IO

Public MustInherit Class MessageBase

    Private Shared MessageLookup As Dictionary(Of Integer, Type)

    Shared Sub New()

        MessageLookup = New Dictionary(Of Integer, Type) 'Key is the Global Message Type defined by the FIT standard

        MessageLookup.Add(GlobalMessageNumberEnum.FILE_ID, GetType(FileIdMessage))
        MessageLookup.Add(GlobalMessageNumberEnum.SESSION, GetType(SessionMessage))
        MessageLookup.Add(GlobalMessageNumberEnum.LAP, GetType(LapMessage))
        MessageLookup.Add(GlobalMessageNumberEnum.RECORD, GetType(RecordMessage))
        MessageLookup.Add(GlobalMessageNumberEnum.ACTIVITY, GetType(ActivityMessage))

    End Sub

    Public Shared Function Parse(reader As BinaryReader, MessageMap As MessageMap) As MessageBase

        Dim message As MessageBase = Nothing

        'Read the message header (1 byte)
        Dim Header As Byte = reader.ReadByte
        Dim HeaderType As RecordHeaderTypeEnum = If((Header And 128) = 0, RecordHeaderTypeEnum.Normal, RecordHeaderTypeEnum.CompressedTimestamp)

        Dim LocalMessageType As Byte

        If (Header And 128) = 0 Then
            'Normal Header
            LocalMessageType = CByte(Header And &HF) ' local message type is stored in the low nibble of the header
        Else
            'Compressed timestamp header
            LocalMessageType = CByte(Header And 96) >> 5
        End If

        If IsDefinitionMessage(Header) Then
            Dim DefMessage As DefinitionMessage = New DefinitionMessage(reader)
            If MessageMap.Definitions.ContainsKey(LocalMessageType) Then
                MessageMap.Definitions(LocalMessageType) = DefMessage
            Else
                MessageMap.Definitions.Add(LocalMessageType, DefMessage)
            End If
            Debug.WriteLine("Read Definition Message, local type=" & LocalMessageType & ", GlobalType=" & DefMessage.GlobalMessageNumber.ToString)
            message = DefMessage
        Else
            If MessageMap.Definitions.ContainsKey(LocalMessageType) Then
                Dim DefMessage As DefinitionMessage = MessageMap.Definitions(LocalMessageType)

                Debug.WriteLine("Reading Data Message, local type=" & LocalMessageType & ", Global type=" & DefMessage.GlobalMessageNumber.ToString)

                If MessageLookup.ContainsKey(DefMessage.GlobalMessageNumber) Then
                    message = CType(Activator.CreateInstance(MessageLookup(DefMessage.GlobalMessageNumber)), MessageBase)
                    message.Populate(reader, DefMessage)
                Else
                    'Unsupported message type,for now just throw out the number of bytes specified in teh size for each field of the def
                    For Each Field As FieldDefinition In DefMessage.Fields
                        For i As Integer = 1 To Field.Size
                            reader.ReadByte()
                        Next
                    Next
                End If
            Else
                Throw New InvalidOperationException("Local message type is not recognized")
            End If
        End If

        Return message
    End Function

    Private Shared Function GetRecordHeaderType(Header As Byte) As RecordHeaderTypeEnum
        If (Header And CInt(2 ^ 7)) > 0 Then
            Return RecordHeaderTypeEnum.CompressedTimestamp
        Else
            Return RecordHeaderTypeEnum.Normal
        End If
    End Function

    Private Shared Function IsDefinitionMessage(Header As Byte) As Boolean
        Return CBool(Header And CInt(2 ^ 6))
    End Function

    Protected Overridable Sub Populate(reader As BinaryReader, Definition As DefinitionMessage)
        'just throw out the number of bytes specified in teh size for each field of the def
        For Each Field As FieldDefinition In Definition.Fields
            FitHelper.ReadUnknownField(reader, Field)
        Next
    End Sub

    Public Enum RecordHeaderTypeEnum As Integer
        Normal = 0
        CompressedTimestamp = 1
    End Enum

    Public Enum LocalMessageTypeEnum As Integer
        DefinitionMessage = 0
        DataMessage = 1
    End Enum
End Class
